# -*- coding: UTF-8 -*-
"""
internal utils
"""
from __future__ import print_function
import argparse
import datetime
import json
import os
import re

from shutil import copyfile
from shutil import rmtree
import shutil
from pathlib import *
import sys
import zipfile
import subprocess
import requests

# pip_install_url = "pip install
# --trusted-host gitlab.mlogcn.com
# --extra-index-url http://gitlab.mlogcn.com:3141 xx"

BASE_DIR, THIS_FILENAME = os.path.split(__file__)
BASE_DIR = os.path.abspath(os.path.join(BASE_DIR, os.pardir))
DEFAULT_VERSION = "0.1.0"


def _check_latest_sdk_(name, pip_host="gitlab.mlogcn.com:3141"):
  """
check latest sdk version,fetch latest sdk version info from pip host.
Args:
  name: sdk name.
  pip_host: pip server.

Returns:
  Latest sdk version.
"""
  local_version = _get_local_version_(name)
  remote_version = _get_remote_version_(name)
  print("local version", local_version, 'remote version', remote_version)
  pip_install_cmd_temp = \
    "pip install -U --trusted-host %s --extra-index-url http://%s %s"
  install_cmd = pip_install_cmd_temp % (pip_host.split(":")[0], pip_host,
                                        name)

  if (local_version is None and remote_version) \
      or (local_version and remote_version
          and local_version < remote_version):
    print(
      'local sdk is too old,please install latest sdk with command:\r\n',
      install_cmd)
    sys.exit(0)
  elif local_version and local_version == remote_version:
    print("module %s is latest " % name)
  elif local_version and not remote_version:
    print("no module named %s found in remote repo" % name)
  else:
    print("no module named %s found" % name)
    sys.exit(-1)


def _extract_gz(fname):
  import tarfile
  if (fname.endswith("tar.gz")):
    tar = tarfile.open(fname, "r:gz")
    tar.extractall()
    tar.close()
  elif (fname.endswith("tar")):
    tar = tarfile.open(fname, "r:")
    tar.extractall()
    tar.close()


def _get_local_version_(name):
  """
get local lastest version.
"""
  cmd = "pip show %s" % name
  print("get local version", cmd)
  for out_str in _exec_cmd_(cmd):
    print(out_str)
    out_str = out_str.decode()
    if out_str.find("Version") >= 0:
      current_version = out_str.split()[1]
      return current_version
  return None


def _get_remote_version_(name, pip_host="http://gitlab.mlogcn.com:3141"):
  """
:param name: sdk name
:param pip_host:  pip server
:return: latest version
"""

  print("get remote version %s" % name)
  # >gavial_pluginsdk-0.1.1.tar.gz</a
  pattern = ('">%s-.+tar.gz</a>' % name)
  pkg_url = pip_host + "/simple/" + name
  rsp = requests.get(pkg_url)
  if rsp.status_code != 200:
    return None
  html = str(rsp.content)
  lines = html.split("\\n")
  versions = []
  for line in lines:
    if line.find(name) >= 0:
      _list = re.findall(pattern, line)
      vstr = [vvstr[2:-4] for vvstr in _list]
      for vvstr in vstr:
        versions.append(vvstr)
  print("remote version list-->", versions)
  # versiont = [v[2:-4] for v in vlist]
  versions.sort()
  if versions:
    vstr = versions[-1][len(name) + 1:-1 * len(".tar.gz")]
    return vstr
  return None


def runProcess(exe):
  p = subprocess.Popen(
    exe,
    stdout=subprocess.PIPE,
    stdin=subprocess.PIPE,
    stderr=subprocess.STDOUT)
  while (True):
    # returns None while subprocess is running
    retcode = p.poll()
    line = p.stdout.readline()
    yield line
    if retcode is not None:
      break


def _exec_cmd_(cmd):
  """
execute command.
:param cmd: command to be executed,array like ['ls','-lrt']
:return:None
"""
  # check_output(cmd)
  p = subprocess.Popen(
    cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  return iter(p.stdout.readline, b'')


def _run_cmd_printout_(cmd):
  """
run command and print stdout.
:param cmd: command to be executed.
:return:None
"""
  for line in _exec_cmd_(cmd):
    print(line)


def _get_job_type_(_dir):
  """
get job type of the plugin.
:param dir: plugin dir
:return: job type code,eg:spark、command.
"""
  _file = open(os.path.join(_dir, "meta.json"), 'rb')
  metas = _file.read()
  json_map = json.loads(metas.decode('utf-8'))
  _file.close()
  return json_map["jobType"]


def package_plugin(_dir_):
  """
package the plugin.
:param _dir_: plugin dir.
:return: None
"""
  _check_syntax_(_dir_)
  _gen_deps_(_dir_)
  _dir_ = os.path.abspath(_dir_)
  __check_meta__(_dir_)
  basedir = os.path.dirname(_dir_)
  name = os.path.basename(_dir_)
  print(basedir, name)
  pkg_name = name + "-" + datetime.datetime.now().strftime('%Y%m%d%H%M%S')
  # check meta.json

  pkg_path = os.path.join(basedir, pkg_name)
  shutil.copytree(_dir_, pkg_path)
  _zip_ = pkg_path + ".zip"
  job_type = _get_job_type_(_dir_)
  if job_type == 'spark':
    if os.path.exists(os.path.join(pkg_path, "dependencies")):
      zipdir(
        os.path.join(pkg_path, "dependencies"),
        os.path.join(pkg_path, 'dependencies.zip'))
      shutil.rmtree(os.path.join(pkg_path, 'dependencies'))
  os.remove(os.path.join(pkg_path, 'local-debug.properties')) \
    if os.path.exists(os.path.join(pkg_path,
                                   'local-debug.properties')) else None
  zipdir(pkg_path, _zip_)
  rmtree(pkg_path)
  print(
    "----------plugin packaged as %s successfully,time to upload" % _zip_)


def create_plugin(plugin_ctx):
  """
create plugin.
:param plugin_ctx: plugin context info.
:return: None.
"""
  if os.path.exists(plugin_ctx.get_name()):
    print("dir exists %s " % plugin_ctx.get_name())
    return -1
  print("-------------create plugin,please wait...")
  sdk_name = plugin_ctx.get_sdk_name()
  _check_latest_sdk_(sdk_name)
  latest_version = _get_local_version_(sdk_name)
  if latest_version is None:
    print("sdk not found %s" % sdk_name)
    return -1
  plugin_dir = os.path.join(plugin_ctx.get_plugin_basedir(),
                            plugin_ctx.get_name())
  os.makedirs(plugin_dir)
  os.mkdir(os.path.join(plugin_dir, "doc"))
  os.mkdir(os.path.join(plugin_dir, "pylint"))
  initpy = open(os.path.join(plugin_dir, "__init__.py"), 'w+')
  initpy.close()
  __write_yapf__(plugin_dir)
  meta_temp_path = os.path.join(BASE_DIR, 'data', 'plugin_template',
                                'meta.json_template')
  shutil.copy(
    os.path.join(BASE_DIR, 'data', 'pylint', 'google-pylintrc'),
    os.path.join(plugin_dir, "pylint", "pylintrc"))
  print("meta_temp_path", meta_temp_path)
  meta_template = __read_file__(meta_temp_path)
  meta_json = __replace_template__(meta_template, plugin_ctx.get_args())
  __write_to_file__(
    os.path.join(plugin_ctx.get_plugin_basedir(), plugin_ctx.get_name(),
                 "meta.json"), meta_json)
  # write useage.md
  __write_to_file__(
    os.path.join(plugin_ctx.get_plugin_basedir(), plugin_ctx.get_name(),
                 "doc", "usage.md"), """
## 功能概述

> 该插件的主要功能概述

## 使用方法

1、使用方法描述1

2、使用方法描述2""")

  # write chang log filie`i
  parts = [
    plugin_ctx.get_plugin_basedir(),
    plugin_ctx.get_name(),
    "/doc/%s-changeLog.md",
  ]
  __write_to_file__(
    os.path.join(plugin_ctx.get_plugin_basedir(), plugin_ctx.get_name(),
                 "doc", "%s-changeLog.md") % plugin_ctx.get_version(), """
--新建插件
  """)

  # write default Param and start.sh
  if plugin_ctx.get_job_type() == "command":
    copyfile(
      os.path.join(BASE_DIR, "data", "plugin_template", "start_%s.sh") %
      plugin_ctx.get_lang(),
      os.path.join(plugin_ctx.get_plugin_basedir(),
                   plugin_ctx.get_name(), "start.sh"))
  copyfile(
    os.path.join(BASE_DIR, "data", "plugin_template",
                 "defaultParam_%s.properties" % plugin_ctx.get_job_type()),
    os.path.join(plugin_ctx.get_plugin_basedir(), plugin_ctx.get_name(),
                 "defaultParam.properties"))

  # write local-debug.properties
  copyfile(
    os.path.join(BASE_DIR, "data", "plugin_template",
                 "local-debug.properties"),
    os.path.join(plugin_ctx.get_plugin_basedir(), plugin_ctx.get_name(),
                 "local-debug.properties"))

  # write py template
  if plugin_ctx.get_lang() == "python":
    # write pkg_provided.txt
    copyfile(
      os.path.join(BASE_DIR, "data", "plugin_template",
                   "pkg_provided.txt"),
      os.path.join(plugin_ctx.get_plugin_basedir(),
                   plugin_ctx.get_name(), "pkg_provided.txt"))
    template = __read_file__(
      os.path.join(
        BASE_DIR, "data", "plugin_template", "template_%s" %
                                             ("python"
                                              if plugin_ctx.get_job_type() == "command" else "spark")))
    template = __replace_template__(template, plugin_ctx.get_args())
    __write_to_file__(
      os.path.join(plugin_ctx.get_plugin_basedir(),
                   plugin_ctx.get_name(), "template.py"), template)

    print("--------------plugin template created successfully")


def __write_yapf__(_dir):
  yapf = """
[style]
# YAPF uses the google style
based_on_style = google
spaces_before_comment = 4
split_before_logical_operator = true

    """
  __write_to_file__(os.path.join(_dir, ".style.yapf"), yapf)


def __replace_template__(template, values):
  """
replace template with values.
:param template:template str.
:param values:values
:return:new string.
"""
  for k in values:
    if not values[k]:
      continue
    template = template.replace("{%s}" % k, values[k])
  return template


class PluginCtx(object):
  """
plugin context info.
"""

  def __init__(self, args):
    self.args = args

  def get_args(self):
    """
get args.
:return: all args.
"""
    return self.args

  def get_name(self):
    """
get plugin name.
:return: plugin name.
"""
    return self.args["NAME"]

  def get_version(self):
    """
get plugin version.
:return: plugin version.
"""
    return self.args["VERSION"]

  def get_user(self):
    """
get user.
:return: user.
"""
    return self.args["USER"]

  def get_lang(self):
    """
get plugin language.
:return: language,eg:python、shell
"""
    return self.args["LANG"]

  def get_job_type(self):
    """
get job type code.
:return:job type code,eg:spark、command.
"""
    return self.args["JOBTYPE"]

  def get_pkg_type(self):
    """
get plugin package type.
:return:plugin package type,eg:ETL、FILEPIPE.
"""
    return self.args["PKGTYPE"]

  def get_sdk_name(self):
    """
get plugin sdk name.
:return:plugin sdk name,default is gavial_plugin.
"""
    return self.args["SDK"]

  # used for plugin creation
  def get_plugin_basedir(self):
    """
get plugin base dir.
:return: plugin base dir.
"""
    return self.args["PLUGIN_BASEDIR"]

  # used for pkg dir
  def get_plugin_dir(self):
    """
get plugin dir.
:return: plugin dir.
"""
    return self.args["PLUGIN_DIR"]


def __write_to_file__(_file_, content, charset='utf-8'):
  """
write content to file.
:param f: file
:param content: content
:return: None
"""
  fwriter = open(_file_, "wb+")
  fwriter.write(content.encode(charset))
  fwriter.close()


def __read_file__(_file_, charset='utf-8'):
  """
read whole file.
:param f:file.
:return:file content.
"""
  freader = open(_file_, "rb")
  content = freader.read().decode(charset)
  freader.close()
  return content


def zipdir(path, dist):
  """
zip compress dir.
:param path: dir path
:param dist: dest path
:return: None
"""
  # ziph is zipfile handle
  ziph = zipfile.ZipFile(dist, 'w')
  ziph.compression = zipfile.ZIP_DEFLATED
  for root, _, files in os.walk(path):
    for _file_ in files:
      if root == path:
        arc = _file_
      else:
        arc = os.path.join(
          root.replace(path + os.path.sep, ''), _file_)
      ziph.write(
        os.path.join(root, _file_),
        arc,
        compress_type=zipfile.ZIP_DEFLATED)
  ziph.close()


def __parse_args__(argsstr):
  """
parse command line args.
:param argsstr:args string
:return:None
"""
  args = argsstr.split()
  idx = 0
  found = False
  for i in range(0, len(args)):
    if args[i] == '--sdk':
      idx = i + 1
      found = True
      break
  if not found:
    sdk = "gavial_pluginsdk"
  else:
    sdk = args[idx]
  print('sdk', sdk)
  latest_version = _get_local_version_(sdk)
  print('local version:', latest_version)
  parser = argparse.ArgumentParser(description='PluginUtil version 0.2.0')
  parser.add_argument(
    "--name", action="store", dest="NAME", help="plugin name")
  parser.add_argument(
    "--version",
    action="store",
    dest="VERSION",
    default="v1.0-3.0",
    help="plugin version,like v1.0-3.0,default v1.0-3.0")
  parser.add_argument(
    "--sdkVersion",
    action="store",
    dest="SDKVERSION",
    default=latest_version,
    help="sdk version,default latest version")
  parser.add_argument(
    "--lang",
    action="store",
    dest="LANG",
    default="python",
    help="language,python、shell,default python")
  parser.add_argument(
    "--jobType",
    dest="JOBTYPE",
    default="command",
    help="job type, command spark,default command")
  parser.add_argument(
    "--pkgType",
    dest="PKGTYPE",
    default="ETL",
    help="plugin pkg type,ETL FILEPIPE,default ETL")
  parser.add_argument(
    "--sdk",
    dest="SDK",
    default="gavial_pluginsdk",
    help="sdk name,default gavial_pluginsdk")
  parser.add_argument(
    "--dir",
    dest="PLUGIN_BASEDIR",
    default=".",
    help="plugin base dir,default current dir")
  parser.add_argument(
    "-p",
    dest="PLUGIN_DIR",
    default=None,
    help="package the plugin for deployment")
  # --name --version --sdkVersion latest --lang
  #  --jobType command --pkgType ETL
  if not argsstr:
    parser.print_help(sys.stderr)
    sys.exit(1)
  args = parser.parse_args(argsstr.split())
  res = {}
  res["LANG"] = args.LANG
  res["NAME"] = args.NAME
  res["VERSION"] = args.VERSION
  res["SDKVERSION"] = args.SDKVERSION
  res["JOBTYPE"] = args.JOBTYPE
  res["PKGTYPE"] = args.PKGTYPE
  res["SDK"] = args.SDK
  res["PLUGIN_BASEDIR"] = args.PLUGIN_BASEDIR
  res["PLUGIN_DIR"] = args.PLUGIN_DIR
  import getpass
  user = getpass.getuser()
  res["USER"] = user
  return PluginCtx(res), parser


def _check_syntax_(_dir_):
  """
check plugin syntax.
:param _dir_: plugin dir.
:return: check status.
"""
  print("-------------run static checks ...")
  cmd = "pylint  --rcfile %s %s" % (os.path.join(_dir_, "pylint",
                                                 "pylintrc"), _dir_)
  check_ok = True
  for line in _exec_cmd_(cmd):
    line = line.decode()
    if line.startswith("E: "):
      __print_red_line__(line)
      check_ok = False
    else:
      print(line)
  if not check_ok:
    __print_red_line__("-----------some static checks failed...")
    exit(-1)


def __print_red_line__(line):
  """
print line with red color.
:param line: line string.
:return: None.
"""
  from colorama import init
  init()
  from colorama import Fore, Style
  print(Fore.RED + line)
  print(Style.RESET_ALL)


def _gen_deps_(_dir_):
  """
generate dependencies.
:param _dir_:plugin dir.
:return:deps.
"""
  print("------------gen dependencies for plugin")
  dep_dir = os.path.join(_dir_, "dependencies")
  req_path = os.path.join(_dir_, "requirements.txt")
  if os.path.exists(dep_dir) and os.path.isdir(dep_dir):
    rmtree(dep_dir)
  if os.path.exists(req_path) and os.path.isfile(req_path):
    os.remove(req_path)
  # gen requirements
  _run_cmd_printout_(
    'pipreqs --force --encoding=utf8 --use-local %s' % _dir_)
  # exclude some req
  reqs = {}
  excludes = {}
  requirements = os.path.join(_dir_, 'requirements.txt')
  if os.path.exists(requirements):
    for line in open(requirements, encoding='utf-8', closefd=True):
      if line.startswith("gavial_pluginsdk"):
        continue
      reqs[line.split("==")[0]] = line
  pkg_provided = os.path.join(_dir_, 'pkg_provided.txt')
  if os.path.exists(pkg_provided):
    for line in open(
        os.path.join(_dir_, 'pkg_provided.txt'), encoding='utf-8'):
      excludes[line.split("=")[0]] = line
    for k in excludes:
      m = reqs.pop(k, None)
      if m:
        print("exclude provided dependency:" + m)
  _file_ = open(
    os.path.join(_dir_, "requirements.txt"), mode="w", encoding='utf-8')
  for k in reqs:
    _file_.write(reqs[k] + "\r\n")
  _file_.close()
  # install dependencies
  if reqs:
    _run_cmd_printout_('pip install -t %s -r %s' % (dep_dir, req_path))
    print("----------project dependencies generated")


def __check_meta__(_dir_):
  """
check plugin meta.json format.
:param _dir_:plugin dir.
:return:check status.
"""
  if not os.path.exists(os.path.join(_dir_, "meta.json")):
    print("meta.json dose not exists")
    exit(1)
  try:
    _file_ = open(os.path.join(_dir_, "meta.json"), 'rb')
    data = _file_.read()
    json.loads(data.decode('utf-8'))
  except ValueError as error:
    print("invalid meta json: %s" % error)
    exit(1)
